﻿/********************************************************************************
 * Custom tool for generating entities and partial class from .dbml
 * Author: Papn Kukn (papnkukn@gmail.com)
 * Modified: 2009-07-22
 * Comments: design a .dbml document in Visual Studio
 *           (e.g. add a new item "LINQ to SQL Classes")
 *           then instead of using internal custom tool use this tool
 *           to generate code for Silverlight and SilverDatabase library
 *           
 * Sample .dbml:
    <?xml version="1.0" encoding="utf-8"?>
    <Database Class="ModelDataContext" xmlns="http://schemas.microsoft.com/linqtosql/dbml/2007">
      <Table Name="" Member="Customers">
        <Type Name="Customer">
          <Column Name="Id" AutoSync="Never" Type="System.Int32" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
          <Column Name="Name" Type="System.String" CanBeNull="false" />
        </Type>
      </Table>
    </Database>
 ********************************************************************************/

/* Custom tool for VS
 * http://www.drewnoakes.com/snippets/WritingACustomCodeGeneratorToolForVisualStudio/
 * http://www.codeproject.com/KB/dotnet/Custom_Tool_Registration.aspx
 * http://www.codeproject.com/KB/dotnet/customtoolsexp.aspx
 * http://stackoverflow.com/questions/894806/run-a-visual-studio-custom-tool-at-build-time
 * 
 * Silverlight client-side database
 * http://silverdb.codeplex.com/
 * http://pietschsoft.com/post/2008/10/Silverlight-Client-Side-Database-via-LINQ-and-Isolated-Storage.aspx
 * */

using System;
using System.IO;
using System.Text;
using System.Xml;

namespace CustomTool
{
	public class DbmlFactory
	{
		/// <summary>
		/// Generates code for database entities.
		/// </summary>
		/// <param name="dbml">Content of .dbml file as string.</param>
		/// <returns>Returns generated code as string.</returns>
		/// <remarks>
		/// Generated code sample:
		///   using System;
		///
		///   namespace SilverlightDatabase
		///   {
		///      public class Customer
		///      {
		///         public int Id { get; set; }
		///         public string Name { get; set; }
		///      }
		///   }
		/// </remarks>
		public static string GenerateEntities(string dbml)
		{
			// Template code, [Entities] is replaced with the generated code
			string template =
@"using System;

namespace Sonce.Editor.Data
{
[Entities]
}";

			StringBuilder builder = new StringBuilder();

			// Load .dbml content
			XmlDocument doc = new XmlDocument();
			dbml = dbml.Replace("xmlns=", "xmlns:ignore="); //Remove xmlns for easier parsing
			doc.LoadXml(dbml);

			// Get table member names
			XmlNodeList tableNodes = doc.SelectNodes("/Database/Table[@Member]");
			foreach (XmlNode tableNode in tableNodes)
			{
				// Get "/Type[@Name]"
				if (tableNode.ChildNodes.Count > 0 && tableNode.ChildNodes[0].LocalName == "Type" && tableNode.ChildNodes[0].Attributes["Name"] != null)
				{
					XmlNode typeNode = tableNode.ChildNodes[0];

					/*
							// Sample
							public class Customer : IPrimaryKey
							{
									public int Id { get; set; }
									public string Name { get; set; }
							}
					*/

					string typeName = typeNode.Attributes["Name"].Value;

					builder.AppendLine("\tpublic class " + typeName + " : IPrimaryKey");
					builder.AppendLine("\t{");

					foreach (XmlNode columnNode in typeNode.ChildNodes)
					{
						if (columnNode.LocalName == "Column" && columnNode.Attributes["Name"] != null)
						{
							string columnName = columnNode.Attributes["Name"].Value;
							string columnType = (columnNode.Attributes["Type"] != null ? columnNode.Attributes["Type"].Value : "object");
							builder.AppendLine("\t\tpublic " + columnType + " " + columnName + " { get; set; }");
						}
					}

					builder.AppendLine("\t}");
					builder.AppendLine("\t"); //Empty line
				}
			}

			// Put generated properties in the template
			string result = template.Replace("[Entities]", builder.ToString());
			return result;
		}

		/// <summary>
		/// Generates code for design-time accessible tables.
		/// </summary>
		/// <param name="dbml">Content of .dbml file as string.</param>
		/// <returns>Returns generated code as string.</returns>
		/// <remarks>
		/// Generated code sample:
		///   using System;
		///
		///   namespace SilverlightDatabase
		///   {
		///      public partial class Database
		///      {
		///         public Table&lt;Customer&gt; Customers
		///         {
		///             get { return this.Table&lt;Customer&gt;() }
		///         }
		///      }
		///   }
		/// </remarks>
		public static string GeneratePartialDatabaseClass(string dbml)
		{
			// Code for partial Database class, [Properties] is replaced with the generated code
			string template =
@"using System;
using System.Collections.Generic;

namespace Sonce.Editor.Data
{
    public partial class EditorDataContext
    {
[Properties]
    }
}";

			StringBuilder builder = new StringBuilder();

			// Load .dbml content
			XmlDocument doc = new XmlDocument();
			dbml = dbml.Replace("xmlns=", "xmlns:ignore="); //Remove xmlns for easier parsing
			doc.LoadXml(dbml);

			// Get table member names
			XmlNodeList tableNodes = doc.SelectNodes("/Database/Table[@Member]");
			foreach (XmlNode tableNode in tableNodes)
			{
				// Get "/Type[@Name]"
				if (tableNode.ChildNodes.Count > 0 && tableNode.ChildNodes[0].LocalName == "Type" && tableNode.ChildNodes[0].Attributes["Name"] != null)
				{
					XmlNode typeNode = tableNode.ChildNodes[0];

					/*
							// Sample
							private static Table<Customer> _Customers = new Table<Customers>();
							public Table<Customer> Customers
							{
									get { return _Customers; }
							}
					*/

					string member = tableNode.Attributes["Member"].Value;
					string name = typeNode.Attributes["Name"].Value;
					string type = string.Format("Table<{0}>", name);

					builder.AppendLine("\t\t" + string.Format("private static {0} _{1} = new {0}();", type, member));
					builder.AppendLine("\t\t" + string.Format("public {0} {1}", type, member));
					builder.AppendLine("\t\t{");
					builder.AppendLine("\t\t\tget { return _" + member + "; }");
					builder.AppendLine("\t\t}");
					builder.AppendLine("\t\t"); //Empty line
				}
			}

			// Put generated properties in the template
			string result = template.Replace("[Properties]", builder.ToString());
			return result;
		}
	}
}
